`include "defines.v"

module if_stage(
    input  wire                             clk,
    input  wire                             rst,

    input  wire                             stall_i,
    input  wire                             flush_i,

    input  wire                             transfer_flag_i,
    input  wire [`RAM_ADDR_WIDTH - 1 : 0]   transfer_address_i,

    input  wire                             exception_flag_i,
    input  wire [`RAM_ADDR_WIDTH - 1 : 0]   exception_addr_i,

    output wire [`RAM_ADDR_WIDTH - 1 : 0]   iram_addr,
    input  wire [`RAM_DATA_WIDTH - 1 : 0]   iram_rdata,
    output wire                             iram_valid,
    input  wire                             iram_ready,
    input  wire                             inst_prepare,

    output wire                             stall_pc_flag_o,    
    output wire                             flush_if_id_flag_o,


    output wire [`RAM_ADDR_WIDTH - 1 : 0]   inst_addr,
    output wire [`INST_WIDTH - 1: 0]        inst
);
    reg  [`RAM_ADDR_WIDTH - 1 : 0] pc;
    
    wire [`RAM_ADDR_WIDTH - 1 : 0] pc_add_4;
    wire [`RAM_ADDR_WIDTH - 1 : 0] pc_value;

    assign pc_add_4 = pc + 4;
    assign pc_value = exception_flag_i  ? exception_addr_i   :
                      transfer_flag_i   ? transfer_address_i :
                                          pc_add_4;

    // fetch an instruction
    always@(posedge clk)
    begin
        //flush_i用不到
        if(rst == 1'b1 || flush_i == 1'b1)
        begin
            pc <= `PC_START;
        end
        else if(stall_i == 1'b1 && exception_flag_i == 1'b0 && transfer_flag_i == 1'b0)
        begin
            pc <= pc;
        end
        else if(stall_i == 1'b1 && exception_flag_i == 1'b1)//可能不会出现这种情况，因为异常在wb阶段出发，此时如果mem是访存指令也会被freeze，不会发送请求。
        begin
            pc <= exception_addr_i - 4;
        end
        else if(stall_i == 1'b1 && transfer_flag_i == 1'b1)//当mem是访存指令时会暂停mem前的流水阶段，若exe是跳转指令，需要保存要跳转的地址
        begin
            pc <= transfer_address_i - 4;
        end
        else
        begin
            pc <= pc_value;
        end
    end

    reg [1:0] fetch_state;

    always @(posedge clk) begin
        if(rst == 1'b1) begin
            fetch_state <= 0;
        end 
        else begin
            case (fetch_state)
                2'b00: begin
                    if((transfer_flag_i | exception_flag_i) == 1'b1 && iram_ready == 1'b0) begin
                        fetch_state <= 2'b11; 
                    end
                    else if((transfer_flag_i | exception_flag_i) == 1'b1 && iram_ready == 1'b1) begin
                        fetch_state <= 2'b10;
                    end
                    else if(iram_ready == 1'b1) begin
                        fetch_state <= 2'b01;
                    end
                end
                2'b11: begin
                    if(iram_ready) begin
                        fetch_state <= 2'b10;
                    end
                end
                2'b01: begin
                    if((transfer_flag_i | exception_flag_i) == 1'b1 && inst_prepare == 1'b0) begin
                        fetch_state <= 2'b10;
                    end
                    else if(inst_prepare) begin
                        fetch_state <= 2'b00;
                    end
                end
                2'b10: begin
                    if(inst_prepare) begin
                        fetch_state <= 2'b00;
                    end
                end
                default:;
            endcase
        end 
    end
    //
    //
    // to axi
    assign iram_addr  = pc;
    // fetch_state == 2'b00 || fetch_state == 2'b11
    assign iram_valid = rst == 1'b1 ? 0 : fetch_state[1] ^~ fetch_state[0];
    //
    //
    // to ctrl
    assign stall_pc_flag_o = ~inst_prepare;
    assign flush_if_id_flag_o = fetch_state == 2'b01 && inst_prepare == 1'b1 && (transfer_flag_i | exception_flag_i) == 1'b0 ? 1'b0 : 1'b1;
    //
    //
    //to if_id
    assign inst      = pc[2] == 1'b1 ? iram_rdata[63:32] : iram_rdata[31: 0];
    // assign inst = iram_rdata[31: 0];
    assign inst_addr = pc;
endmodule